Skip to content

SwiftUI模型嵌套的监听问题

转载: 原文地址 zhuanlan.zhihu.com

@ObservedObject @Published 以及 ObservableObject 是 SwiftUI 常用的数据组织和监听方式。

一般写法如下,当 foo 发生 “改变” 时,将触发 MyView 的一次重新渲染。

class Foo: ObservableObject {
    @Published var bar: String = 1
}

struct MyView: View {
    @ObservedObject var foo: Foo

    var body: some View {
        //
    }
}
class Foo: ObservableObject {
    @Published var bar: String = 1
}

struct MyView: View {
    @ObservedObject var foo: Foo

    var body: some View {
        //
    }
}

但随着项目扩大,需要对数据进行分拆、整理,重新组织,可能需要放在多个 ObservableObject 中。在写 Vue 项目时,几乎都会使用 Vuex 来组织和管理数据,于是想在 SwiftUI 中也套用类似的数据组织方式,在项目扩大时,将 store 中的数据分拆成多个 module。

尝试在 SwiftUI 中实现,当代码运行时,会发现结果和预想并不相同。

class Foo: ObservableObject {
    @Published var bar: Bar
}

class Bar: ObservableObject {
    @Published var baz: String
}

struct MyView: View {
    @ObservedObject var foo: Foo

    var body: some View {
        //
    }
}
class Foo: ObservableObject {
    @Published var bar: Bar
}

class Bar: ObservableObject {
    @Published var baz: String
}

struct MyView: View {
    @ObservedObject var foo: Foo

    var body: some View {
        //
    }
}

当 baz 发生改变时,MyView 并没有刷新,那么是哪里出了问题呢?

class Foo: ObservableObject {
    @Published var bar: Bar = Bar()

    private var anyCancellable: AnyCancellable?

    init() {
        self.anyCancellable = self.objectWillChange.sink {
            print("foo changed")
        }
    }
}

class Bar: ObservableObject {
    @Published var baz: String = ""

    private var anyCancellable: AnyCancellable?
    
    init() {
        self.anyCancellable = self.objectWillChange.sink {
            print("bar changed")
        }
    }
}

struct MyView: View {
    @ObservedObject var foo: Foo = Foo()

    var body: some View {
        Text("xxx")
    }
}
class Foo: ObservableObject {
    @Published var bar: Bar = Bar()

    private var anyCancellable: AnyCancellable?

    init() {
        self.anyCancellable = self.objectWillChange.sink {
            print("foo changed")
        }
    }
}

class Bar: ObservableObject {
    @Published var baz: String = ""

    private var anyCancellable: AnyCancellable?
    
    init() {
        self.anyCancellable = self.objectWillChange.sink {
            print("bar changed")
        }
    }
}

struct MyView: View {
    @ObservedObject var foo: Foo = Foo()

    var body: some View {
        Text("xxx")
    }
}

通过日志会发现,当 baz 变化时打印了 "bar changed",而没有 "foo changed"。

  1. 首先,ObservableObject 提供了 objectWillChange 这个 Publisher 用于通知订阅者发生了变化,View 的刷新应该也依赖于此。
  2. 其次,Published 所提供的 Publisher 会触发 ObservableObject 所提供的 Publisher 发送更新消息。

因此,问题就在于,从 Bar 向外传递的更新消息 “断了”,Published 不太聪明,虽然懂得如何判断一个 String 是否发生了更新,但无法判断一个 ObservableObject 是否发生了变化。

因此就需要我们来 “助它一臂之力”。

class Foo: ObservableObject {
    @Published var bar: Bar = Bar()

    private var anyCancellable: AnyCancellable?

    init() {
        self.anyCancellable = self.bar.objectWillChange.sink {
            self.objectWillChange.send()
        }
    }
}

class Bar: ObservableObject {
    @Published var baz: String = ""
}

struct MyView: View {
    @ObservedObject var foo: Foo = Foo()

    var body: some View {
        Text("xxx")
    }
}
class Foo: ObservableObject {
    @Published var bar: Bar = Bar()

    private var anyCancellable: AnyCancellable?

    init() {
        self.anyCancellable = self.bar.objectWillChange.sink {
            self.objectWillChange.send()
        }
    }
}

class Bar: ObservableObject {
    @Published var baz: String = ""
}

struct MyView: View {
    @ObservedObject var foo: Foo = Foo()

    var body: some View {
        Text("xxx")
    }
}

当监听到 bar 发生了变化时,也触发 foo 的更新信息。

当有多个需要监听的 ObservableObject 时,可以使用不同的方法对消息管道进行整合,这部分和 Reactive 写法相似,有必要可以看看文档,或者了解一下 Reactive。